package com.cyj.springbootservice.controller.nearby;

import ch.hsr.geohash.GeoHash;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cyj.springbootservice.api.entity.PostInfo;
import com.cyj.springbootservice.service.PostInfoService;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.io.GeohashUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @description: 附近的人
 * <p>
 * MySQL+GeoHash
 * <p>
 * GeoHash被广泛应用于位置搜索类的业务中，本文不对它进行展开说明，有兴趣的同学可以看一下这篇博客:[https://www.cnblogs.com/LBSer/p/3310455.html，https://www.cnblogs.com/larscheng/p/12063409.html]
 * <p>
 * 使用Mysql存储用户信息，其中包括用户的经纬度信息和geoHash字符串。
 * <p>
 * 添加新用户时计算该用户的geoHash字符串，并存储到用户表中
 * <p>
 * 当要查询某一gps附近指定距离的用户信息时，通过比对geoHash误差表确定需要的geoHash字符串精度
 * <p>
 * 计算获得某一精度的当前坐标的geoHash字符串，通过 WHERE geoHash Like 'geoHash%'来查询数据集
 * <p>
 * 如果geoHash字符串的精度远大于给定的距离范围时，查询出的结果集中必然存在在范围之外的数据
 * <p>
 * 计算两点之间距离，对于超出距离的数据进行剔除。
 * @Author C_Y_J
 * @create 2021/10/18 9:41
 **/
@RestController
@RequestMapping("/post2")
public class PostInfo2Controller {

    @Autowired
    private PostInfoService postInfoService;

    private SpatialContext spatialContext = SpatialContext.GEO;

    /**
     * 保存
     *
     * @param postInfo
     * @return
     */
    @PostMapping("/save")
    public boolean save(@RequestBody PostInfo postInfo) {
        //默认精度12位
        String geoHashCode = GeohashUtils.encodeLatLon(postInfo.getLatitude(), postInfo.getLongitude());
        return postInfoService.save(postInfo.setGeoHash(geoHashCode));
    }

    /**
     * 获取附近x米的人
     *
     * @param distance      距离范围 单位km
     * @param precision     geoHash的精确度
     * @param userLongitude 当前经度
     * @param userLatitude  当前纬度
     * @return json
     */
    @GetMapping("/nearby")
    public List<PostInfo> nearBySearch2(@RequestParam("distance") double distance,
                                        @RequestParam("precision") int precision,
                                        @RequestParam("userLongitude") double userLongitude,
                                        @RequestParam("userLatitude") double userLatitude) {

        //1.根据要求的范围，确定geoHash的精确度，获取到当前用户坐标的geoHash码
        String geoHashCode = GeohashUtils.encodeLatLon(userLatitude, userLongitude, precision);

        //2.匹配指定精度的geoHash码
        List<PostInfo> postInfoList = postInfoService.lambdaQuery().likeRight(PostInfo::getGeoHash, geoHashCode).list();

        //3.过滤超出距离的
        List<PostInfo> collect = postInfoList.stream().filter(
                a -> getDistance(
                        a.getLongitude(), a.getLatitude(), userLongitude, userLatitude
                ) <= distance
        ).collect(Collectors.toList());

        return collect;
    }

    /**
     * 获取附近x米的人
     *
     * @param distance      距离范围 单位km
     * @param precision     geoHash的精确度
     * @param userLongitude 当前经度
     * @param userLatitude  当前纬度
     * @return json
     */
    @GetMapping("/nearby1")
    public List<PostInfo> nearBySearch3(@RequestParam("distance") double distance,
                                        @RequestParam("precision") int precision,
                                        @RequestParam("userLongitude") double userLongitude,
                                        @RequestParam("userLatitude") double userLatitude) {


        // 根据要求的范围，确定geoHash码的精度，获取到当前用户坐标的geoHash码
        GeoHash geoHash = GeoHash.withCharacterPrecision(userLatitude, userLongitude, precision);

        // 获取到用户周边8个方位的geoHash码
        GeoHash[] adjacent = geoHash.getAdjacent();

        LambdaQueryWrapper<PostInfo> queryWrapper = new QueryWrapper<PostInfo>().lambda().likeRight(PostInfo::getGeoHash, geoHash.toBase32());
        Stream.of(adjacent).forEach(a -> queryWrapper.or().likeRight(PostInfo::getGeoHash, a.toBase32()));

        // 匹配指定精度的geoHash码
        List<PostInfo> postInfoList = postInfoService.list(queryWrapper);

        // 过滤超出距离的
        List<PostInfo> collect = postInfoList.stream().filter(
                a -> getDistance(
                        a.getLongitude(), a.getLatitude(), userLongitude, userLatitude
                ) <= distance
        ).collect(Collectors.toList());

        return collect;
    }


    /***
     * 球面中，两点间的距离
     *
     * @param longitude 坐标1 经度
     * @param latitude  坐标1 纬度
     * @param userLongitude   坐标2 用户经度
     * @param userLatitude   坐标2 用户纬度
     * @return 返回距离，单位km
     */
    private double getDistance(Double longitude, Double latitude, double userLongitude, double userLatitude) {
        return spatialContext.calcDistance(
                spatialContext.makePoint(userLongitude, userLatitude),
                spatialContext.makePoint(longitude, latitude)
        ) * DistanceUtils.DEG_TO_KM;
    }
}
